package pl.edu.pw.polygen.modeler.client.object;

import java.util.ArrayList;
import java.util.List;

import org.vaadin.gwtgraphics.client.VectorObject;
import org.vaadin.gwtgraphics.client.shape.Circle;
import org.vaadin.gwtgraphics.client.shape.Text;

import pl.edu.pw.polygen.modeler.client.graph.BaseModel;
import pl.edu.pw.polygen.modeler.client.graph.GraphHandler;
import pl.edu.pw.polygen.modeler.client.graph.IPropertyChangeListener;
import pl.edu.pw.polygen.modeler.client.graph.VertexModel;
import pl.edu.pw.polygen.modeler.client.object.listener.IPolygenObjectListener;
import pl.edu.pw.polygen.modeler.client.object.listener.LockedVertexListener;
import pl.edu.pw.polygen.modeler.client.object.listener.MergeVertexListener;
import pl.edu.pw.polygen.modeler.client.object.listener.PolygenObjectListenerMultiton;
import pl.edu.pw.polygen.modeler.client.object.listener.SelectableVertexListener;
import pl.edu.pw.polygen.modeler.client.utils.Scale;
import pl.edu.pw.polygen.modeler.client.workspace.IWorkspace;

import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;

public class Vertex extends Circle implements IPolygenObject, IPropertyChangeListener{

	public enum VertexType{NORMAL_VERTEX, CONTROL_VERTEX, SUBDOMAIN_VERTEX, HOLE_VERTEX};
	private Vertex instance;
	private IWorkspace context;
	private VertexModel vertexModel;
	private List<IPolygenObject> polygenParents;
	private Scale scale;
	private IPolygenObjectListener polygenObjectListener;
	private boolean selected;
	private boolean holded;
	private VertexType vertexType;
	private Text boundaryText;
	
	protected static int DEFAULT_RADIUS = 2;
	protected static int HOVER_RADIUS = 3;
	protected static int SELECTED_RADIUS = 4;
	protected static String DEFAULT_COLOR = "white";
	protected static String HOVER_COLOR = "green";
	protected static String SELECTED_COLOR = "red";
	
	public Vertex(int x, int y, IWorkspace workspace,VertexType type){
		super(x,y,DEFAULT_RADIUS);
		vertexType = type;
		polygenParents = new ArrayList<IPolygenObject>();
		boundaryText = new Text(x, y-5, "");
		boundaryText.setFontSize(10);
		boundaryText.setVisible(workspace.getBoundariesVisibility());
		scale = Scale.getInstance();
		context = workspace;
		this.initializeModel();
		this.initStates();
		this.initListeners();
		instance = this;
	}
	
	private void initializeModel(){
		vertexModel = new VertexModel();
		GraphHandler.getInstance(context.getId()).addVertex(vertexModel);
		vertexModel.addPropertyChangeListener(this);
		double x = scale.getModelXValue(getX());
		double y = scale.getModelYValue(getY());
		vertexModel.setPosition(x, y);
		vertexModel.setVertexModelType(vertexType);
		addToGraph();
	}
	
	public void setModelPosition(double x, double y){
		vertexModel.setPosition(x, y);
	}
	
	private void addToGraph(){
		GraphHandler.getInstance(context.getId()).addVertex(vertexModel);
	}
	
	private void initStates(){
		this.selected = false;
		this.holded = false;
	}
	
	private void initListeners(){
		setLocked();
		MouseActions mouseActions = new MouseActions();
		this.addMouseOutHandler(mouseActions);
		this.addMouseOverHandler(mouseActions);
		this.addMouseDownHandler(mouseActions);
		this.addMouseUpHandler(mouseActions);
		this.addMouseMoveHandler(mouseActions);
	}
	
	
	@Override
	public void initialize() {
		initListeners();
	}
	
	@Override
	public IWorkspace getContext() {
		return context;
	}
	
	@Override
	public void setContext(IWorkspace context){
		this.context = context;
	}

	@Override
	public void notifyChanges() {
		int x = scale.getViewXValue(vertexModel.getX());
		int y = scale.getViewYValue(vertexModel.getY());
		setX(x);
		setY(y);
		if(context.getBoundariesVisibility()){
			boundaryText.setVisible(true);
			boundaryText.setX(x);
			boundaryText.setY(y-5);
			if(vertexModel.getBoundary()!=0){
				boundaryText.setText("("+vertexModel.getBoundary()+")");
			}else{
				boundaryText.setText("");
			}
		}else{
			boundaryText.setVisible(false);
		}
	}
	
	@Override
	public void draw(){
		if(isHolded()){
			int x = context.getMouseX();
			int y = context.getMouseY();
			setPosition(x, y);
			context.showProperties(this);
		}
	}

	@Override
	public void add() {
		if(context.contains(this)==false){
			addToCanvas();
			context.add(this);
		}
	}

	@Override
	public void remove() {
		context.remove(this);
		removeFromCanvas();
	}
	
	@Override
	public void addToCanvas() {
		context.getCanvas().add(this);
		context.getCanvas().add(boundaryText);
	}

	@Override
	public void removeFromCanvas() {
		context.getCanvas().remove(this);
		context.getCanvas().remove(boundaryText);
	}

	@Override
	public void setPosition(int x, int y) {
		double nx = scale.getModelXValue(x);
		double ny = scale.getModelYValue(y);
		vertexModel.setPosition(nx, ny);
	}

	@Override
	public boolean isHolded() {
		return holded;
	}

	@Override
	public void hold() {
		selected = false;
		holded = true;
		setRadius(HOVER_RADIUS);
		setFillColor(HOVER_COLOR);
		context.addToHolded(this);
		
	}

	@Override
	public void drop() {
		holded = false;
		selected = false;
		instance.setRadius(DEFAULT_RADIUS);
		instance.setFillColor(DEFAULT_COLOR);
		context.removeFromHolded(this);
	}

	@Override
	public boolean isSelected() {
		return selected;
	}

	@Override
	public void select() {
		selected = true;
		holded = false;
		setRadius(SELECTED_RADIUS);
		setFillColor(SELECTED_COLOR);
		context.addToSelected(this);
	}

	@Override
	public void unselect() {
		holded = false;
		selected = false;
		instance.setRadius(DEFAULT_RADIUS);
		instance.setFillColor(DEFAULT_COLOR);
		context.removeFromSelected(this);
	}
	
	@Override
	public void over() {
		setRadius(HOVER_RADIUS);
	}

	@Override
	public void out() {
		setRadius(DEFAULT_RADIUS);
	}
	
	@Override
	public void setPolygenObjectListener(IPolygenObjectListener polygenObjectListener) {
		this.polygenObjectListener = polygenObjectListener;
	}
	
	@Override
	public void setSelectable() {
		if (vertexType != VertexType.CONTROL_VERTEX)
			this.setPolygenObjectListener(PolygenObjectListenerMultiton
					.getInstance(SelectableVertexListener.class));
	}

	@Override
	public void setMergeable() {
		if (vertexType != VertexType.CONTROL_VERTEX)
			this.setPolygenObjectListener(PolygenObjectListenerMultiton
					.getInstance(MergeVertexListener.class));
	}

	@Override
	public void setLocked() {
		if (vertexType != VertexType.CONTROL_VERTEX)
			this.setPolygenObjectListener(PolygenObjectListenerMultiton
					.getInstance(LockedVertexListener.class));
	}
	
	
	
	/* 
	 * source object will be deleted. All observers will be saved to target
	 */
	@Override
	public void merge(IPolygenObject source, IPolygenObject target) {
		Vertex targetVertex = (Vertex)target;
		Vertex sourceVertex = (Vertex)source;
		List<IPropertyChangeListener> sourceListeners = sourceVertex.getModel().getPropertyChangeListener();
		for(IPropertyChangeListener listener : sourceListeners){
			targetVertex.getModel().addPropertyChangeListener(listener);
		}
		GraphHandler.getInstance(source.getContext().getId()).removeVertex((VertexModel)sourceVertex.getModel());
		source.remove();
		List<IPolygenObject> polygenObjects = sourceVertex.getPolygenParents();
		if(polygenObjects!=null){
			for(IPolygenObject polygenObject : polygenObjects){
				targetVertex.addPolygenParent(polygenObject);
				polygenObject.merge(sourceVertex, targetVertex);
			}
		}
	}
	
	@Override
	public void setModel(BaseModel model) {
		if(model instanceof VertexModel){
			this.vertexModel = (VertexModel) model;
		}
	}

	@Override
	public BaseModel getModel() {
		return vertexModel;
	}
	

	@Override
	public List<IPolygenObject> getPolygenParents() {
		return polygenParents;
	}

	@Override
	public void addPolygenParent(IPolygenObject polygenParent) {
		if(!polygenParents.contains(polygenParent))
			polygenParents.add(polygenParent);
	}
	

	@Override
	public void removePolygenParent(IPolygenObject polygenParent) {
		if(polygenParents.contains(polygenParent))
			polygenParents.remove(polygenParent);
	}
	

	private void moveToBottom(){
		int size = context.getCanvas().getVectorObjectCount();
		for (int i = size - 1; i > 0; i--) {
			VectorObject object = context.getCanvas().getVectorObject(i);
			if(!(object instanceof Vertex) && !(object instanceof Text)){
				this.setParent(null);
				context.getCanvas().insert(this,i+1);
				break;
			}
		}
	}
	
	
	
	@Override
	public void showBoundary() {
		this.boundaryText.setVisible(true);
	}

	@Override
	public void hideBoundary() {
		this.boundaryText.setVisible(false);
	}

	public VertexType getVertexType() {
		return vertexType;
	}

	public void setVertexType(VertexType vertexType) {
		this.vertexType = vertexType;
	}

	private class MouseActions implements MouseOutHandler,MouseOverHandler, MouseDownHandler,MouseUpHandler, MouseMoveHandler{

		@Override
		public void onMouseDown(MouseDownEvent event) {		
			moveToBottom();
			polygenObjectListener.onMouseDown(event,instance);
		}
		
		@Override
		public void onMouseUp(MouseUpEvent event) {
			polygenObjectListener.onMouseUp(event, instance);
		}


		@Override
		public void onMouseOver(MouseOverEvent event) {
			polygenObjectListener.onMouseOver(event, instance);
		}

		@Override
		public void onMouseOut(MouseOutEvent event) {
			polygenObjectListener.onMouseOut(event, instance);
		}

		@Override
		public void onMouseMove(MouseMoveEvent event) {
			polygenObjectListener.onMouseMove(event, instance);
		}
		
	}

}
